home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Add-Ons / After Dark / The Swarm 1.0 / Source (THINK C 6.0) / The Swarm.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-03-22  |  35.7 KB  |  1,041 lines  |  [TEXT/R*ch]

  1. /*
  2.  *    The Swarm 1.0 - an After Dark module by Leo Breebaart, Kronto Software 1994.
  3.  *
  4.  *
  5.  *      For non-technical information about this module and for the credits,
  6.  *    see the README file.
  7.  *       
  8.  *       This module displays a variable number of line-segments (the ‘Bees’), which 
  9.  *       chase another line segment (the ‘Queen’) across the screen.
  10.  *       
  11.  *       I have commented the code in a way that experienced programmers may find
  12.  *       overkill, but it was done in the hope that beginning After Dark programmers
  13.  *       will find “The Swarm” a useful starting point for writing their own 
  14.  *       modules.
  15.  *       
  16.  *       For general information about how to write an After Dark module, 
  17.  *       see the After Dark Programmer’s Manual, but make sure you have the most recent
  18.  *       version (released with After Dark 2.0u and later). The comments in this
  19.  *    module do assume you are at least *aware* of the basic After Dark mechanisms.
  20.  *
  21.  *      Here we go...
  22.  */
  23.  
  24. #include <QDoffscreen.h>
  25.  
  26. #include "AfterDarkTypes.h"
  27.     
  28.     // Interface to Jonas Englund's CLUT fade library for nice fade-in/fade-out effects.
  29. #include "fade.h"
  30.  
  31.  
  32.     // Constants
  33. #define MAXBEES 100            // Maximum number of Bees.
  34. #define QUEENVEL 12            // Maximum Queen velocity (in pixels, as are the others).
  35. #define QUEENACC 5            // Maximum Queen acceleration.
  36. #define BEEVEL  11            // Maximum Bee velocity.
  37. #define BEEACC  3            // Maximum Bee acceleration.
  38. #define BORDER  50            // Queen won't go any nearer than this many pixels to the
  39.                             // edge of the screen and the demo rectangle.
  40.                         
  41.     // Data structures
  42. typedef int    **IntHandle;        // Our Bee position arrays will be dynamical arrays
  43.                                 // allocated using handles.
  44.  
  45. typedef struct SwarmStorage        // All data we use is stored in this struct.
  46. {
  47.     int queenX[2], queenY[2];        // Storage for Queen line segment.
  48.     int queenVelX, queenVelY;        // Current Queen velocity components.
  49.     IntHandle beeX[2], beeY[2];        // Dynamic storage for Bee line segments.
  50.     IntHandle beeVelX, beeVelY;        // Dynamic storage for Bee velocities components.
  51.  
  52.     Rect swarmRect;                 // Bounding rectangle for swarm at time '0'.
  53.     Rect oldRect;                    // Bounding rectangle for swarm at time '1'.
  54.     
  55.     int maxQueenVel, maxBeeVel;        // These variables store the constants
  56.     int maxQueenAcc, maxBeeAcc;        // defined earlier.
  57.     int border;                
  58.     
  59.     int nBees;                        // The current number of active Bees.
  60.     long delay;                        // The current animation speed depends on this value.
  61.     long startDrawing;                // Works together with delay to control animation speed.
  62.     Boolean demoMode;                // Are we currently in After Dark demo Mode?
  63.  
  64.     RGBColor whiteRGB, blackRGB;    // Color QuickDraw colors: white and black.
  65.     RGBColor queenRGB, beeRGB;        // Color QuickDraw colors for Queen and Bees.
  66.     RGBColor backRGB;                // Color QuickDraw color for animation background.
  67.       
  68.     Rect monitorRect;                // Rectangle corresponding to main monitor.
  69.     int winW, winH;                    // The info about monitorRect we really need.
  70.  
  71.        GWorldPtr gMyOffG;                // A pointer to an offscreen graphics world.
  72. }
  73. SwarmStorage, *SwarmStoragePtr;
  74.  
  75.                             
  76.     // Here we define some handy macro's for accessing the 
  77.     // Swarm's position and velocity values. These macro's work under the
  78.     // assumption that you have a handle variable named 'swarm', pointing
  79.     // to a storage struct as defined above.
  80.  
  81.     // Position of Bee b at time t (either 0 or 1).
  82. #define BX(t,b)  (*((*(swarm->beeX[t]))+(b)))
  83. #define BY(t,b)  (*((*(swarm->beeY[t]))+(b)))
  84.  
  85.     // Current velocity of Bee b
  86. #define BXV(b)    (*((*swarm->beeVelX)+(b)))
  87. #define BYV(b)    (*((*swarm->beeVelY)+(b)))
  88.  
  89.     // Position of Queen at time t (either 0 or 1).
  90. #define QX(t)  (swarm->queenX[t])
  91. #define QY(t)  (swarm->queenY[t])
  92.  
  93.     // Current velocity of Queen
  94. #define QXV    (swarm->queenVelX)
  95. #define QYV    (swarm->queenVelY)
  96.  
  97.     // A comment about this 'time t' business: each swarm member's
  98.     // line segment is drawn 'from' position 0 to position 1. So
  99.     // t=1 is the 'old' position, t=0 the 'new' position. After one
  100.     // frame of animation, e.g. QX(0) will be assigned to QX(1) (making
  101.     // that the 'old' position), and QX[0] will get a new value. In other 
  102.     // words, every swarm element essentially traces a continuous path
  103.     // across the screen.
  104.     
  105.     
  106.     // Some more handy macro's.
  107.  
  108. #define RAND(v) (RangedRdm(-(v)/2, (v)/2))        // Random integer around 0.  
  109. #define abs(x) (((x) > 0) ? (x) : -(x))            // Standard macro for 'abs'.
  110.  
  111. #define setmin(x, min) if (x < min) min = x;    // Keep track of a minimum value.
  112. #define setmax(x, max) if (x > max) max = x;    // Keep track of a maxmimum value.
  113.  
  114.     // After Dark's way of showing an error to the user.
  115.     // '##' is an ANSI-ism meaning meta-concatenation.
  116. #define ErrorMsg(m) BlockMove(CtoPstr(m), params->errorMessage, 1 + m##[0]);
  117.  
  118.     // In order to avoid unexpected but unpleasant surprises...
  119. #define SafeDisposHandle(h) if ((Handle)(h)) DisposeHandle((Handle)(h))
  120.  
  121.     
  122.     // Let's be good ANSI citizens, and define some function prototypes.
  123.     // These functions will be called from the AfterDarkShell.c code. 
  124. OSErr DoInitialize(Handle *storage, RgnHandle blankRgn, GMParamBlockPtr params);
  125. OSErr DoClose(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params);
  126. OSErr DoBlank(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params);
  127. OSErr DoDrawFrame(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params);
  128. OSErr DoHelp(RgnHandle blankRgn, GMParamBlockPtr params);
  129.  
  130.     // Some local functions of my own.
  131. OSErr    AboutBoxError(Rect graf);
  132. int     RangedRdm(int min, int max);
  133. Handle    BestNewHandle(Size s);
  134. Boolean XYInRect(Rect *r, int x, int y);
  135.  
  136.  
  137.     // Now we come the fun part: the actual implementations of the functions.
  138.     // First: DoInitialize, which allocates our swarm data structure as defined
  139.     // above, initializes the variables in that struct, and does a gazillion checks
  140.     // for possible problems.
  141.  
  142. OSErr
  143. DoInitialize(Handle *storage, RgnHandle blankRgn, GMParamBlockPtr params)
  144. {
  145.     register SwarmStoragePtr swarm;        // The swarm, obviously
  146.     register int b;                        // Counter for Bee loops
  147.         
  148.         // Local variables in order to avoid having to write
  149.         // 'swarm->...' all the time. Could have used macro's here as
  150.         // well, I suppose.
  151.     RGBColor whiteRGB, blackRGB, queenRGB, beeRGB;
  152.     int nBees;
  153.     long delay;
  154.     
  155.         // If After Dark is in demo-mode, the demoRect can be found in the
  156.         // params argument to this function, but we need to make temporary
  157.         // changes to it, so we use a local copy.
  158.     Rect borderedDemoRect;    
  159.  
  160.  
  161.          // Our offscreen graphics worlds need Color QuickDraw.
  162.     if (!params->colorQDAvail)
  163.     {
  164.         DisposHandle(*storage);
  165.         ErrorMsg("The Swarm:  Sorry, I need Color QuickDraw to run!");
  166.         return ModuleError;
  167.     }
  168.     
  169.         // Allocate 'master' handle to the storage struct.
  170.     if ((*storage = BestNewHandle(sizeof(SwarmStorage))) == NULL)
  171.     {     
  172.         ErrorMsg("The Swarm:  Couldn't allocate enough memory!");
  173.         return ModuleError;
  174.     }
  175.  
  176.         // Lock down the storage so we can refer to it by pointer. 
  177.     HLockHi(*storage);
  178.     swarm = (SwarmStoragePtr) **storage;
  179.         
  180.         
  181.         // Set up and initialize the colors we use.
  182.                     
  183.         // Absolute black and white.
  184.     whiteRGB.red = whiteRGB.green = whiteRGB.blue = 0xFFFF;
  185.     blackRGB.red = blackRGB.green = blackRGB.blue = 0;
  186.     
  187.         // A golden color for the Bees, a bright red for the Queen.
  188.     beeRGB.red = 0xFFFF; beeRGB.green = 0x9C9C; beeRGB.blue = 0x0808;
  189.     queenRGB.red = 0xBDBD; queenRGB.green = 0; queenRGB.blue = 0;
  190.     
  191.         // The Queen's red color gets mapped to black by the Mac
  192.         // if the screen is less then 8-bit. We don't want that...
  193.     if (params->monitors->monitorList[0].curDepth < 4)
  194.         queenRGB = whiteRGB;
  195.  
  196.         // This is a very crude and stupid way to try and detect if the main screen
  197.         // is in grayscale mode. If so, I make the entire animation white-on-black,
  198.         // since anything else is guaranteed to make either the queen or the drones
  199.         // look to dark, even under 256 grayshades.
  200.         // In the next version, this will all be solved much cleaner, together
  201.         // with the currently unsupported case of multiple-monitor setups.
  202.     if (!(params->systemConfig & (1L << 3)))
  203.     {
  204.         beeRGB = queenRGB = whiteRGB;
  205.     }
  206.     
  207.     swarm->whiteRGB = whiteRGB;
  208.     swarm->blackRGB = blackRGB;
  209.     swarm->queenRGB = queenRGB;
  210.     swarm->beeRGB   = beeRGB;
  211.     swarm->backRGB  = blackRGB;
  212.     
  213.         // We get our number of bees from a slide control in the
  214.         // After Dark control panel interface. This number can be zero!
  215.     swarm->nBees = nBees = params->controlValues[0];
  216.     
  217.         // We get the animation speed from a second slide control.
  218.         // This is primarily intended for Macs which are *too fast*,
  219.         // which is why we express speed in terms of the delay between frames.
  220.         // This delay will be either 0, 2, 4, 6, 8 or 10 system ticks long.
  221.     swarm->delay = delay = (long)10 - (2*params->controlValues[1] / 20);
  222.     
  223.         // Sanity checks. These things should never occur.
  224.     if (nBees < 0 || nBees > 100)
  225.     {
  226.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  227.         ErrorMsg("The Swarm:  Internal Error — insane number of bees!");
  228.         return ModuleError;
  229.     }
  230.     
  231.     if (delay < 0 || delay > 10)
  232.     {
  233.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  234.         ErrorMsg("The Swarm:  Internal Error — insane bee speed value!");
  235.         return ModuleError;
  236.     }
  237.     
  238.         // See DoDrawFrame for more info on how this variable is used to implement
  239.         // the delay.
  240.     swarm->startDrawing = Ticks;
  241.  
  242.         // We need to know if we are in demo Mode in the DoDrawFrame function,
  243.         // but we want to avoid having to call the EmptyRect function every time,
  244.         // so we store it in a Boolean here.
  245.     swarm->demoMode = !EmptyRect((¶ms->demoRect));
  246.         
  247.         // Allocate handles, and afterwards (a) check if the allocation
  248.         // succeeded, and (b) move the handles to high memory and lock them.
  249.         // I could have done this with static arrays (beeX[2][MAXBEES]),
  250.         // I suppose, but I wanted to learn how to work with handles.
  251.         // The speed difference in access is, to my surprise, negligible,
  252.         // So I see no reason to change it back.
  253.         //
  254.         // Notice that we allocate enough memory for the maximum number
  255.         // of Bees in advance. This is because in Demo Mode we want to
  256.         // be able to let the user dynamically change nBees, which is
  257.         // the *current* number of Bees, if you'll remember. In order to
  258.         // do that neatly, we want to have the storage ready and initialized
  259.         // beforehand.
  260.         
  261.     swarm->beeX[0] = (IntHandle) BestNewHandle(MAXBEES*sizeof(int));
  262.     swarm->beeX[1] = (IntHandle) BestNewHandle(MAXBEES*sizeof(int));
  263.     swarm->beeY[0] = (IntHandle) BestNewHandle(MAXBEES*sizeof(int));
  264.     swarm->beeY[1] = (IntHandle) BestNewHandle(MAXBEES*sizeof(int));
  265.  
  266.     swarm->beeVelX = (IntHandle) BestNewHandle(MAXBEES*sizeof(int));
  267.     swarm->beeVelY = (IntHandle) BestNewHandle(MAXBEES*sizeof(int));
  268.     
  269.     if (!(swarm->beeX[0] && swarm->beeX[1] && swarm->beeY[0] && swarm->beeY[1]
  270.           && swarm->beeVelX && swarm->beeVelY))
  271.     {
  272.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  273.         ErrorMsg("The Swarm:  Couldn't allocate enough internal memory!");
  274.         return ModuleError;
  275.     }
  276.     
  277.     HLockHi((Handle) swarm->beeX[0]);
  278.     HLockHi((Handle) swarm->beeX[1]);
  279.     HLockHi((Handle) swarm->beeY[0]);
  280.     HLockHi((Handle) swarm->beeY[1]);
  281.     
  282.     HLockHi((Handle) swarm->beeVelX); 
  283.     HLockHi((Handle) swarm->beeVelY);
  284.  
  285.         // Initialize the random number generator.
  286.     params->qdGlobalsCopy->qdRandSeed = TickCount();
  287.  
  288.         // Swarm animation uses *only* the main monitor (...[0]).
  289.         // This will change in the next release of this module.
  290.     swarm->monitorRect = params->monitors->monitorList[0].bounds;
  291.     swarm->winW   = swarm->monitorRect.right - swarm->monitorRect.left;     
  292.     swarm->winH   = swarm->monitorRect.bottom - swarm->monitorRect.top;
  293.     
  294.         // For different monitor sizes, we want the relation between
  295.         // border and winW to be the same as that between BORDER and 640
  296.         // (my 13" screen). The cast to 'long' is because the multiplication
  297.         // will overflow a 2-byte int for large monitors. You have no *idea*
  298.         // how long it took me to discover this one (primarily because I don't
  299.         // *have* a large monitor).
  300.     swarm->border = ((long) BORDER * swarm->winW) / 640;
  301.     
  302.         // Initialise variables with earlier defined constants.
  303.     swarm->maxQueenVel = QUEENVEL;
  304.     swarm->maxBeeVel  = BEEVEL;
  305.     swarm->maxQueenAcc = QUEENACC;
  306.     swarm->maxBeeAcc  = BEEACC;
  307.     
  308.         // Make both bounding rectangles intially empty.
  309.     SetRect(&swarm->oldRect, 0, 0, 0, 0);
  310.     SetRect(&swarm->swarmRect, 0, 0, 0, 0);
  311.         
  312.         // Initial Queen position.
  313.     QX(0) = RangedRdm(swarm->border, swarm->winW - swarm->border);
  314.     QY(0) = RangedRdm(swarm->border, swarm->winH - swarm->border);
  315.  
  316.         // If in demo mode, then make sure that the
  317.         // initial Queen position lies at least 'border' pixels *outside* the demoRect!
  318.     if (swarm->demoMode)
  319.     {
  320.         borderedDemoRect = params->demoRect;
  321.         InsetRect(&borderedDemoRect, -swarm->border, -swarm->border);
  322.  
  323.         while (XYInRect(&borderedDemoRect, QX(0), QY(0)))
  324.         {
  325.             QX(0) = RangedRdm(swarm->border, swarm->winW - swarm->border);
  326.             QY(0) = RangedRdm(swarm->border, swarm->winH - swarm->border);
  327.         }
  328.     }
  329.  
  330.         // For the first frame, our line segment is just a point.
  331.     QX(1) = QX(0);
  332.     QY(1) = QY(0);
  333.     QXV = QYV = 0;
  334.     
  335.         // Ditto for the Bees, although (a) all Bees start from the same
  336.         // physical position as the Queen, and (b) all Bees have a different
  337.         // initial velocity. This gives a nice 'fountaining' effect on startup
  338.         // of the animation.
  339.     for (b = 0; b < MAXBEES; b++)
  340.     {
  341.         BX(0,b) = QX(0);
  342.         BX(1,b) = BX(0,b);
  343.         BY(0,b) = QY(0);
  344.         BY(1,b) = BY(0,b);
  345.  
  346.         BXV(b) = RAND(7);
  347.         BYV(b) = RAND(7);
  348.     }
  349.         
  350.         // Now allocate an offscreen graphics world, where we can do
  351.         // fast drawing without disturbing the real screen.
  352.         // I recommend looking up this call in Inside Macintosh or Think Reference!
  353.     //if (NewGWorld(&(swarm->gMyOffG), 0, &((**blankRgn).rgnBBox), nil, nil, noNewDevice) != noErr)      
  354.     //if (NewGWorld(&(swarm->gMyOffG), 0, &(swarm->monitorRect), nil, nil, noNewDevice) != noErr)      
  355.     if (NewGWorld(&(swarm->gMyOffG), 0, &(swarm->monitorRect), nil, nil, noNewDevice+useTempMem) != noErr)      
  356.     {     
  357.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  358.         ErrorMsg("The Swarm:  Not enough memory for offscreen graphics world!");
  359.         return ModuleError;            
  360.     }
  361.  
  362.     HUnlock(*storage);
  363.         
  364.     return noErr;
  365. }
  366.  
  367.  
  368.     // Next, the DoBlank function. This function performs two tasks: it blanks
  369.     // out the real screen (on all monitors, not just the main monitor we use for the
  370.     // animation), and it also blanks out the offworld screen.
  371.  
  372. OSErr
  373. DoBlank(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  374. {
  375.     register SwarmStoragePtr swarm;
  376.         
  377.     GWorldPtr        currPort;    // The 'real' screen consists of two components
  378.     GDHandle        currDev;    // which we'll save in these variables.
  379.  
  380.     HLockHi(storage);
  381.     swarm = (SwarmStoragePtr) *storage;
  382.     
  383.         // Save the 'real' screen.
  384.     GetGWorld(&currPort,&currDev);
  385.     
  386.         // Switch to the offscreen world.
  387.     SetGWorld((swarm->gMyOffG), nil);
  388.     
  389.         // Set the backgroundcolor, and erase the whole world.
  390.        RGBBackColor(&swarm->backRGB);
  391.     EraseRgn(blankRgn);
  392.     
  393.         // Switch back to the 'real' screen.
  394.     SetGWorld (currPort, currDev);
  395.     
  396.         // This is not redundant! We are in a different world now...
  397.        RGBBackColor(&swarm->backRGB);
  398.  
  399.         // Do a nice fade-out/fade-in if the user specified
  400.         // that check box in the Control Panel, and if the monitor
  401.         // is capable of doing so.
  402.         // The routines I use are *only* fit for 8-bit CLUT displays, so
  403.         // don't ever try changing this!
  404.     if (params->controlValues[2] && params->monitors->monitorList[0].curDepth == 8)
  405.     {
  406.         fade_screen(200,true);
  407.         EraseRgn(blankRgn);
  408.            fade_screen(64,false);
  409.        }
  410.        else
  411.            EraseRgn(blankRgn);
  412.            
  413.        HUnlock(storage);
  414.  
  415.     return noErr;
  416. }
  417.  
  418.  
  419.     // Ha, finally we come to the meat of our module: the DoDrawFrame function.
  420.     // Although there is a lot of code here, what actually happens is quite simple.
  421.     // First, all the position and velocity variables are updated and checked for
  422.     // bouncing etc. Then, we *erase* (in the offscreen world) the 'old' swarm
  423.     // by filling the swarm's bounding rectangle (which will vary from frame to frame!)
  424.     // with the background color. Then, we draw the 'new' swarm. Finally, we use
  425.     // the famous 'CopyBits' function to move the changed areas of the offscreen
  426.     // world to the real screen world. And that's all.
  427.     
  428.     // One more comment: a lot of the array accesses could have been optimized
  429.     // to make the code run faster. Local variables could have been used to avoid
  430.     // the pointer arithmetic caused by all those global handles and arrays.
  431.     // The BlockMove function could have been called to updates entire arrays in
  432.     // one sweep.
  433.     // However, all those accesses taken together still take up only a negligable fraction
  434.     // of this function's total execution time, when compared to the cost of
  435.     // doing the graphics. CopyBits is an expensive function, and for large numbers
  436.     // of Bees the drawing of the line segments takes even more time.
  437.     // This is why I have decided to refrain from optimizing. It doesn't matter very
  438.     // much speedwise, and such optimizations would only obscure the underlying algorithm.
  439.     
  440. OSErr
  441. DoDrawFrame(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  442. {        
  443.     register SwarmStoragePtr swarm;
  444.     
  445.         // Again, lots of local variables in order to avoid
  446.         // too much 'swarm->...' stuff.
  447.     RGBColor beeRGB, queenRGB, whiteRGB, backRGB;
  448.     Rect monitorRect, *swarmRect, *oldRect;
  449.     int winW, winH, nBees, border;
  450.     long delay, dummy;        // 'dummy' variable is unused, but needed
  451.                             // for Delay toolbox call.
  452.     
  453.         // Variables used for calculating the swarm's bounding rectangle.
  454.     int xMin=10000, xMax=-10000, yMin=10000, yMax=-10000;
  455.  
  456.     GWorldPtr currPort;        // The 'real' screen consists of two components.
  457.     GDHandle  currDev;        // which we'll save in these variables.
  458.     
  459.         // The actual pixel maps of offscreen and real screen, respectively.
  460.     PixMapHandle offBase, realBase;
  461.  
  462.         // Various rectangles...:-)
  463.     Rect unionRect, targetRect, helpRect, pixRect, screenRect, tmpRect;
  464.     
  465.         // Distances from a Bee to the Queen.
  466.     int dx, dy, distance;            
  467.  
  468.         // Bee counter for loops.
  469.     register int b;
  470.     
  471.         // storage was already locked in DoInitialize, so this is safe.
  472.     swarm = (SwarmStoragePtr) *storage;
  473.     
  474.     nBees = swarm->nBees;
  475.     delay = swarm->delay;
  476.  
  477.         // If we are in Demo mode, reread the nBees and delay values -- the user may have
  478.         // changed them dynamically.
  479.     if (swarm->demoMode)
  480.     {
  481.         if (nBees != params->controlValues[0])
  482.             nBees = swarm->nBees = params->controlValues[0];
  483.  
  484.         if (delay != params->controlValues[1]) 
  485.             delay = swarm->delay = (long)10 - (2*params->controlValues[1] / 20);
  486.  
  487.     }
  488.         // The next thing to do is to check for delay. This can be done
  489.         // naively by simply using the Delay system function (with 'swarm->delay' as
  490.         // parameter), but that would mean that we would simply spend those
  491.         // ticks *doing nothing* -- and not allowing anything else to do anything
  492.         // either. That's why we do it differently: if the delay has not passed yet,
  493.         // we simply exit from this function at once, and wait until we are called again.
  494.         // That way, almost all the delay time is given to the After Dark parent
  495.         // process which can then presumeably use it to check for system activity etc.
  496.         
  497.     if (delay != 0)
  498.         if (Ticks < swarm->startDrawing)
  499.             return noErr;
  500.         else
  501.             swarm->startDrawing = Ticks + delay;
  502.     
  503.         // Initialize the other local variables from their swarm counterparts.
  504.     monitorRect = swarm->monitorRect;
  505.     winW   = swarm->winW;
  506.     winH   = swarm->winH;
  507.     border = swarm->border;
  508.     
  509.     backRGB  = swarm->backRGB;
  510.     whiteRGB = swarm->whiteRGB;
  511.     queenRGB = swarm->queenRGB;
  512.     beeRGB   = swarm->beeRGB;
  513.     
  514.     swarmRect = &swarm->swarmRect;
  515.     oldRect   = &swarm->oldRect;
  516.     
  517.         // Age the swarm bouding rectangle.
  518.     *oldRect = *swarmRect;
  519.     
  520.         // First, we do the Queen Stuff:
  521.  
  522.         // Age the position arrays. 
  523.     QX(1) = QX(0);
  524.     QY(1) = QY(0);
  525.  
  526.         // Accelerate.
  527.     QXV += RAND(swarm->maxQueenAcc);
  528.     QYV += RAND(swarm->maxQueenAcc);
  529.  
  530.         // Speed limit checks.
  531.     if (QXV > swarm->maxQueenVel)
  532.         QXV = swarm->maxQueenVel;
  533.     else 
  534.         if (QXV < -swarm->maxQueenVel)
  535.             QXV = -swarm->maxQueenVel;
  536.     if (QYV > swarm->maxQueenVel)
  537.         QYV = swarm->maxQueenVel;
  538.     else
  539.         if (QYV < -swarm->maxQueenVel)
  540.             QYV = -swarm->maxQueenVel;
  541.  
  542.         // Fill new 'current' positions.
  543.     QX(0) = QX(1) + QXV;
  544.     QY(0) = QY(1) + QYV;
  545.  
  546.         // Bounce Checks.
  547.     if ((QX(0) < border) || (QX(0) > winW - border - 1))
  548.     {
  549.             // These two statements (and all similar ones further on)
  550.             // cause a swarm element to 'bounce off' according to a
  551.             // "angle of entry is angle of exit" rule. It looks much more
  552.             // cryptic than it really is. Trust me.
  553.         QXV = -QXV;
  554.         QX(0) += QXV << 1;
  555.     }
  556.     if ((QY(0) < border) || (QY(0) > winH - border - 1))
  557.     {
  558.         QYV = -QYV;
  559.         QY(0) += QYV << 1;
  560.     }
  561.     
  562.          // If we are in demo Mode, we want the Queen to 'bounce' off
  563.          // the demoRect as well. This takes some hairy additional testing :-)
  564.     
  565.         // Without these macro's 'hairy' would become 'bloody incomprehensible'.
  566.  #define DL (params->demoRect.left - border/2)
  567.  #define DR (params->demoRect.right + border/2)
  568.  #define DT (params->demoRect.top - border/2)
  569.  #define DB (params->demoRect.bottom + border/2)
  570.  
  571.          // The actual tests.
  572.     if (!EmptyRect(¶ms->demoRect))
  573.     {
  574.          if ((QX(0) < DR) && (QX(0) > DL) && (QY(0) < DB) && (QY(0) > DT))
  575.         {
  576.             if ((QX(1) <= DL) || (QX(1) >= DR))
  577.             {
  578.                 QXV = -QXV;
  579.                  QX(0) += QXV << 1;
  580.              }
  581.              if ((QY(1) <= DT) || (QY(1) >= DB))
  582.              {
  583.                 QYV = -QYV;
  584.                  QY(0) += QYV << 1;
  585.             }
  586.         }
  587.        }
  588.         
  589.         // Keep track of the minimal bouding rect of the swarm so far.
  590.     setmin(QX(0), xMin);
  591.     setmin(QY(0), yMin);
  592.     setmax(QX(0), xMax);
  593.     setmax(QY(0), yMax);
  594.  
  595.     setmin(QX(1), xMin);
  596.     setmin(QY(1), yMin);
  597.     setmax(QX(1), xMax);
  598.     setmax(QY(1), yMax);
  599.     
  600.    
  601.            // Now we get to the Bee stuff, which is basically
  602.            // the same, except that (a) Bees do *not* bounce off walls or
  603.            // demoRects, and (b) Bees will try to 'follow' the Queen.
  604.             
  605.    
  606.         // First, don't ever let things settle down. 
  607.     if (nBees > 0)               // Avoid later division by 0!
  608.     {
  609.         BXV(RangedRdm(0, nBees)) += RAND(3);
  610.         BYV(RangedRdm(0, nBees)) += RAND(3);
  611.     }
  612.     
  613.     for (b = 0; b < nBees; b++)
  614.     {
  615.             // Age the arrays. 
  616.         BX(1, b) = BX(0, b);
  617.         BY(1, b) = BY(0, b);
  618.         
  619.             // Accelerate.
  620.         dx = QX(1) - BX(1, b);
  621.         dy = QY(1) - BY(1, b);
  622.             
  623.             // This calculation of the true distance from the dx/dy values
  624.             // is an approximation that allows us to keep everything in 
  625.             // integer math. Otherwise we'd have do square root operations...
  626.         distance = abs(dx) + abs(dy); 
  627.         if (distance == 0)
  628.             distance = 1;
  629.         BXV(b) += (dx * swarm->maxBeeAcc) / distance;         
  630.         BYV(b) += (dy * swarm->maxBeeAcc) / distance;
  631.  
  632.             // Speed limit checks.
  633.         if (BXV(b) > swarm->maxBeeVel)
  634.             BXV(b) = swarm->maxBeeVel;
  635.         else 
  636.             if (BXV(b) < -swarm->maxBeeVel)
  637.                 BXV(b) = -swarm->maxBeeVel;
  638.         if (BYV(b) > swarm->maxBeeVel)
  639.             BYV(b) = swarm->maxBeeVel;
  640.         else
  641.             if (BYV(b) < -swarm->maxBeeVel)
  642.                 BYV(b) = -swarm->maxBeeVel;
  643.  
  644.             // Fill new 'current' positions.
  645.         BX(0, b) = BX(1, b) + BXV(b);
  646.         BY(0, b) = BY(1, b) + BYV(b);
  647.         
  648.             // Keep track of the minimal bouding rect of the swarm so far.
  649.         setmin(BX(0,b), xMin);
  650.         setmax(BX(0,b), xMax);
  651.         setmin(BX(1,b), xMin);
  652.         setmax(BX(1,b), xMax);
  653.  
  654.         setmin(BY(0,b), yMin);
  655.         setmax(BY(0,b), yMax);
  656.         setmin(BY(1,b), yMin);
  657.         setmax(BY(1,b), yMax);   
  658.     }       
  659.  
  660.         // swarmRect will now be set to the minimal bounding rectangle we've been maintaining,
  661.         // which we want to 'clip' by intersecting it with the screen rectangle,
  662.         // and which we finally need to combine with the *old* bounding rectangle,
  663.         // to give as a result the minimal bounding rectangle of the entire screen
  664.         // area that has been affected by this step of the animation.
  665.         
  666.     SetRect(swarmRect, xMin-1, yMin-1, xMax+1, yMax+1);
  667.     SectRect(&monitorRect, swarmRect, swarmRect);
  668.     UnionRect(oldRect, swarmRect, &unionRect);
  669.     
  670.         // Save the real screen world.
  671.     GetGWorld(&currPort, &currDev);
  672.     
  673.         // Switch to the offscreen world.
  674.     SetGWorld (swarm->gMyOffG, nil);
  675.         
  676.         // Erase the old swarm bounding rectangle.
  677.     RGBBackColor(&swarm->backRGB);
  678.     EraseRect(oldRect);
  679.     
  680.         // Draw the new swarm, first Queen, then Bees.
  681.     RGBForeColor(&queenRGB);
  682.     MoveTo(QX(0), QY(0));
  683.     LineTo(QX(1), QY(1));
  684.  
  685.     RGBForeColor(&beeRGB);
  686.  
  687.     for (b = 0; b < nBees; b++)
  688.     {
  689.         MoveTo(BX(0, b), BY(0, b));
  690.         LineTo(BX(1, b), BY(1, b));
  691.     }
  692.  
  693.         // Switch back to the real screen.    
  694.     SetGWorld(currPort, currDev);               
  695.        
  696.         // Retrieve actual pixel maps for both offscreen and real screen.
  697.     realBase = GetGWorldPixMap((GWorldPtr) currPort);
  698.     offBase  = GetGWorldPixMap(swarm->gMyOffG);
  699.  
  700.            // These next two calls appear to make no sense, but
  701.            // are *absolutely* necessary for CopyBits to function
  702.            // correctly. See the Apple TechNote on this subject (or Inside Macintosh)
  703.     RGBBackColor(&whiteRGB);
  704.     RGBForeColor(&swarm->blackRGB);            
  705.  
  706.        // Blit the changed area from offscreen to realscreen.
  707.     CopyBits((BitMap *) (*offBase), (BitMap *) (*realBase), 
  708.               &unionRect, &unionRect,
  709.               srcCopy, nil);
  710.                    
  711.     return noErr;
  712. }
  713.  
  714.  
  715.     // Well, the hard part is now over -- almost. "The Swarm" also features a
  716.     // funky About Box, which has a miniature version of the swarm animation
  717.     // going on inside of it. Creating that animation was simpler than I feared it would
  718.     // be: for the most part I just call the previous functions, i.e. DoInitialize,
  719.     // DoBlank, and DoDrawFrame -- and that's it. The tricky part lies in getting
  720.     // some correct variables in place, and setting up the right graphics port.
  721.     //
  722.     // One thing you should realize about DoHelp: when After Dark calls this
  723.     // function, it will already have set the currPort and the blankRgn to the help rectangle. 
  724.     // So you are at this point no longer drawing to the entire screen.
  725.     //
  726.     // Final note: if you want to use a DoHelp function yourself, realize that (a) 
  727.     // you need to tell After Dark you want to 'take over' (see the Cals resource in
  728.     // the Programmer's Manual), and (b) 'storage' is *not* initialized in DoHelp,
  729.     // because After Dark calls DoHelp without calling DoInitialize first, in contrast
  730.     // to e.g. DoDrawFrame. Unfortunately, a lot of code shows example 'DoAbout' or
  731.     // 'DoHelp' functions with the 'storage' handle as a parameter. But this parameter
  732.     // will *not* be intialized, and if you use it, you will crash horribly.
  733.  
  734. OSErr
  735. DoHelp(RgnHandle blankRgn, GMParamBlockPtr params)
  736. {
  737.     // If you've read and understood DoInitialize and DoDrawFrame, I think you'll
  738.     // have enough knowledge so that I won't have to annotate every single variable
  739.     // here, right?
  740.  
  741.     SwarmStorage **miniSwarm;
  742.        
  743.     long dummy;
  744.     GrafPtr helpGraf;
  745.     short oldCount;
  746.     char oldFade;
  747.     Rect oldBounds;
  748.     RgnHandle miniBlankRgn = NewRgn();
  749.     RGBColor miniSwarmBackground;
  750.     
  751.        PicHandle helpPict;                
  752.     Rect picRect, helpRect, miniRect;
  753.     
  754.     StringHandle str;
  755.     
  756.         // The miniswarm has a dark blue instead of a black background.
  757.     miniSwarmBackground.red = 0x0808; 
  758.     miniSwarmBackground.green = 0; 
  759.     miniSwarmBackground.blue = 0x2929; 
  760.  
  761.         // Get the real screen.
  762.     GetPort(&helpGraf);
  763.  
  764.         // Get the real screen rectangle. miniRect is the version
  765.         // we’ll use in the rest of this function, helpRect is the
  766.         // ‘original’ version we’ll need to pass to the error handling 
  767.         // routine. Again, that last part is an ugly hack, and will be
  768.         // cleaned up in the next version.
  769.     helpRect = miniRect = helpGraf->portRect;
  770.     LocalToGlobal(&topLeft(helpRect));
  771.     LocalToGlobal(&botRight(helpRect));
  772.  
  773.  
  774.         // Load the 'about' PICT resource.
  775.     if ((helpPict = GetPicture(2000)) == nil) 
  776.     {
  777.         ErrorMsg("The Swarm:  Couldn’t load PICT resource for help picture!");
  778.         return AboutBoxError(helpRect);
  779.     }
  780.     
  781.         // Draw the PICT, and release the resource.
  782.     picRect = (**helpPict).picFrame;
  783.     DrawPicture(helpPict, &picRect);
  784.     ReleaseResource((Handle) helpPict);
  785.  
  786.         // We now manually change the graphics environment
  787.         // from the entire help area to the subarea where we want the
  788.         // mini animation to take place (look at the about box in action if this
  789.         // is not clear to you). The current Port is moved and made smaller.
  790.         // The Toolbox calls take care of all the nasty details.
  791.     LocalToGlobal(&topLeft(miniRect));
  792.     LocalToGlobal(&botRight(miniRect));
  793.     MovePortTo(miniRect.left, miniRect.top+100);
  794.     PortSize(222, 120);
  795.     
  796.         // Save some relevant parameters that the 'real' After Dark
  797.         // animation will need to have restored later on.
  798.     oldCount  = params->monitors->monitorCount;
  799.     oldBounds = params->monitors->monitorList[0].bounds;
  800.     oldFade   = params->controlValues[2];
  801.  
  802.         // Change the parameters temporarily. One monitor (not that the current main
  803.         // animation handles multiple monitors, mind you, but in a
  804.         // future version it will, while here we really want just one screen.
  805.     params->monitors->monitorCount = 1;
  806.     params->monitors->monitorList[0].bounds = helpGraf->portRect;
  807.         // Don't use fading!
  808.     params->controlValues[2] = 0;
  809.     
  810.     RectRgn(miniBlankRgn, &helpGraf->portRect);    
  811.     
  812.         // Initialize the miniSwarm struct. Note that we have a problem
  813.         // with error management here: I can use ErrorMsg all I want,
  814.         // but After Dark will do nothing with the return value of
  815.         // the DoHelp function, for some reason. So I have implemented
  816.         // a special AboutBoxError() function of my own to handle
  817.         // errors.
  818.     if (DoInitialize((Handle *) &miniSwarm, miniBlankRgn, params) != noErr) 
  819.     {
  820.         ErrorMsg("The Swarm:  Initialization of helpSwarm failed!");
  821.         return AboutBoxError(helpRect);
  822.     }
  823.  
  824.         // Change some values to make them better suited for
  825.         // miniature animation. Notice that unlike 'params'
  826.         // before, we do not need to save the old values here,
  827.         // since miniSwarm is entirely local to this function.
  828.     (**miniSwarm).maxQueenVel = 7;
  829.     (**miniSwarm).maxBeeVel   = 6;
  830.     (**miniSwarm).nBees = 20;
  831.  
  832.     (**miniSwarm).backRGB = miniSwarmBackground;
  833.     RGBBackColor(&miniSwarmBackground);
  834.  
  835.         // Blank the miniSwarm region
  836.     if (DoBlank((Handle) miniSwarm, miniBlankRgn, params) != noErr)
  837.     {
  838.         ErrorMsg("The Swarm:  DoBlank of helpSwarm failed!");
  839.         return AboutBoxError(helpRect);
  840.     }
  841.     
  842.         // Wait for the user to release the mouse button, if necessary.
  843.     while (Button())
  844.         ;
  845.         
  846.         // Animate, until the user presses the mouse button.
  847.     while (!Button())
  848.     {
  849.         if (DoDrawFrame((Handle) miniSwarm, miniBlankRgn, params) != noErr)
  850.         {
  851.             ErrorMsg("The Swarm:  DoDrawFrame of helpSwarm failed!");
  852.             return AboutBoxError(helpRect);
  853.         }
  854.             // uncrippled mini-animation is too fast!!
  855.         Delay(3, &dummy);
  856.     }
  857.         // Close it all up.
  858.     DoClose((Handle) miniSwarm, miniBlankRgn, params);
  859.  
  860.         // Restore old params values.
  861.     params->monitors->monitorCount = oldCount;
  862.     params->monitors->monitorList[0].bounds = oldBounds;
  863.     params->controlValues[2] = oldFade;
  864.         
  865.         // Finally, display some text in the area where
  866.         // the animation used to be.
  867.  
  868.     if ((helpPict = GetPicture(2001)) == nil) 
  869.     {
  870.         ErrorMsg("The Swarm:  Couldn’t load PICT resource for help picture!");
  871.         return AboutBoxError(helpRect);
  872.     }
  873.     
  874.     picRect = (**helpPict).picFrame;
  875.     
  876.         // Draw the text PICT in the space left open by the mini animation. 
  877.     MovePortTo(miniRect.left, miniRect.top+96);
  878.     DrawPicture(helpPict, &picRect);
  879.         
  880.     ReleaseResource((Handle) helpPict);
  881.     DisposeRgn(miniBlankRgn);
  882.     DisposeHandle((Handle) miniSwarm);
  883.     
  884.         // Wait for a final mouse click, and then exit.
  885.     while (Button())
  886.         ;
  887.      while (!Button())
  888.          ;
  889.      
  890.      FlushEvents(everyEvent, 0);    
  891.  
  892.     return noErr;
  893. }
  894.     
  895.  
  896.     // The DoClose function merely disposes of all those handles and 
  897.     // offscreen worlds. Nothing interesting here.
  898.  
  899. OSErr
  900. DoClose(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  901. {
  902.     SwarmStorage **swarm = (SwarmStorage **) storage;
  903.     
  904.     if (swarm)
  905.     {
  906.         SafeDisposHandle((**swarm).beeVelX);
  907.         SafeDisposHandle((**swarm).beeVelY);
  908.         SafeDisposHandle((**swarm).beeX[0]);
  909.         SafeDisposHandle((**swarm).beeX[1]);
  910.         SafeDisposHandle((**swarm).beeY[0]);
  911.         SafeDisposHandle((**swarm).beeY[1]);
  912.         
  913.         DisposeGWorld((**swarm).gMyOffG);
  914.         SafeDisposHandle(storage);
  915.     }
  916.  
  917.         // Exit with a fade-out/fade-in effect if wanted and possible.
  918.     if (params->controlValues[2] && params->monitors->monitorList[0].curDepth == 8)
  919.     {
  920.         fade_screen(64,true);
  921.         FillRgn(blankRgn, params->qdGlobalsCopy->qdBlack);
  922.         fade_screen(1,false);
  923.        }
  924.     
  925.     return noErr;
  926. }
  927.  
  928.  
  929.     // This function will one day, in the next version of the swarm, 
  930.     // become a special error manager for handling errors that occur in
  931.     // situations where After Dark itself won’t take action, e.g. in 
  932.     // animated ‘About’ boxes.
  933.     // For now it is a very rudimentary function, which simply draws
  934.     // some text. This is not a good example of how to handle situations
  935.     // like this, but I don’t have time for anything better right now.
  936. OSErr
  937. AboutBoxError(Rect r)
  938. {    
  939.         // These tedious calls simply have the total effect of moving
  940.         // the drawing area back to exactly the part of the screen
  941.         // corresponding with the original About Box contents.
  942.         
  943.     Rect currRect = thePort->portRect;
  944.  
  945.     MovePortTo(r.left, r.top);
  946.     PortSize(r.right-r.left, r.bottom-r.top);
  947.  
  948.     ForeColor(blackColor);
  949.     BackColor(whiteColor);
  950.     
  951.     GlobalToLocal(&topLeft(r));
  952.     GlobalToLocal(&botRight(r));
  953.  
  954.     FillRect(&r, white);
  955.  
  956.     TextFont(geneva);
  957.     TextSize(9);
  958.  
  959.     MoveTo(15,20);
  960.      DrawString("\pI'm sorry — an error has occurred.");
  961.      MoveTo(15,30);
  962.      DrawString("\pNothing serious, mind you. The module");
  963.      MoveTo(15,40);
  964.      DrawString("\pprobably just ran out of memory.");
  965.      MoveTo(15,60);
  966.      DrawString("\pYou see, I have not implemented decent");
  967.      MoveTo(15,70);
  968.      DrawString("\perror management routines for this");
  969.      MoveTo(15,80);
  970.      DrawString("\pAbout Box yet. Next version, I promise.");
  971.      MoveTo(15,100);
  972.      DrawString("\pIf you *do* have lots of memory, but you");
  973.      MoveTo(15,110);
  974.      DrawString("\pstill see this message, then something");
  975.      MoveTo(15,120);
  976.      DrawString("\p*is* probably very wrong, and I would");
  977.      MoveTo(15,130);
  978.      DrawString("\preally appreciate an e-mail bug report.");
  979.      MoveTo(15,150);
  980.      DrawString("\pThanks!");
  981.     SysBeep(1); SysBeep(1);
  982.     
  983.     MovePortTo(currRect.left, currRect.top);
  984.     PortSize(currRect.right-currRect.left, currRect.bottom-currRect.top);
  985.      
  986.     while (Button())
  987.         ;
  988.      while (!Button())
  989.          ;
  990.      
  991.      FlushEvents(everyEvent, 0);    
  992.     return ModuleError;
  993. }
  994.  
  995.     // Random functions yield a number between min and max. The function
  996.     // originated from Think Reference, but this version is an adaptation
  997.     // by Joseph "Peek-a-Boo" Judge. 
  998.  
  999. int RangedRdm(int min, int max)
  1000. {
  1001.     unsigned    qdRdm;
  1002.     long    range, t;
  1003.     
  1004.     qdRdm = Random();
  1005.     range = (max - min) + 1;
  1006.     t = ((long)qdRdm * range) / 65536;     // now 0 <= t <= range 
  1007.     return( t+min );
  1008. }
  1009.  
  1010.  
  1011.     // This function tries to allocate a handle from temporary memory
  1012.     // first, and only if that fails from the reserved memory.
  1013.     // This function is taken from the DarkSide of the Mac example code.
  1014.     // Note that the return value can be nil and should be checked.
  1015. Handle    
  1016. BestNewHandle(Size s)
  1017. {
  1018.     Handle theHandle;
  1019.     OSErr  anErr;
  1020.     
  1021.     if ((theHandle = TempNewHandle(s, &anErr)) == nil)
  1022.         theHandle = NewHandle(s);
  1023.         
  1024.     return(theHandle);
  1025. }
  1026.  
  1027.     // Whew, finally a support function I wrote all by myself!
  1028.     // This next function is like the ToolBox call PtInRect, but works directly
  1029.     // with two x/y values instead of with a Point structure.
  1030.     
  1031. Boolean
  1032. XYInRect(Rect *r, int beeX, int beeY)
  1033. {
  1034.     return (beeX > r->left && beeX < r->right && beeY > r->top && beeY < r->bottom);
  1035. }
  1036.  
  1037.     // This is THE END.
  1038.     // If you've learned anything from this code, or found errors in it, or
  1039.     // have questions about it, or whatever: feel free to drop me a note.
  1040.     // My e-mail address is: leo@cp.tn.tudelft.nl
  1041.